<?php declare(strict_types=1);

namespace application\units;

use LogicException;

class BusinessCommonQuery
{
    public $limit;
    public $offset;
    public $ops;
    public $filters;
    public $sort;
    public $order;
    
    protected $di;
    
    public function __construct(\application\units\Di $di)
    {
        $this->di      = $di;
        $this->filters = $di->request->params('filters', []);
        $this->ops     = $di->request->params('ops', []);
        $this->limit   = $di->request->params('limit', 10);
        $this->offset  = $di->request->params('offset');
        $this->sort    = $di->request->params('sort', '');
        $this->order   = $di->request->params('order', 'ASC');
        
        if (is_string($this->sort)) {
            $this->sort = json_decode($this->sort, true) ?: $this->sort;
        }
        
        if (is_string($this->filters)) {
            $this->filters = json_decode($this->filters, true) ?: [];
        }
        
        if (is_string($this->ops)) {
            $this->ops = json_decode($this->ops, true) ?: [];
        }
        
        if (!is_array($this->filters)) throw new LogicException('filters参数格式错误');
        if (!is_array($this->ops)) throw new LogicException('ops参数格式错误');
    }
    
    public function build(): array
    {
        $ops     = $this->ops;
        $filters = array_filter($this->filters, function ($value) {
            if ($value !== '' && !is_null($value)) {
                return true;
            }
        });
        
        if (is_array($this->sort)) {
            foreach (array_filter($this->sort) as $sort) {
                $aSort     = explode('-', $sort, 2);
                $sortField = $aSort[0];
                $orderWay  = $this->order;
                if (count($aSort) > 1) {
                    $orderWay = $aSort[1];
                }
                if (preg_match('/(^_([a-zA-Z0-9\.]_?)*$)|(^[a-zA-Z](_?[a-zA-Z0-9\.])*_?$)/', $sortField) == false) {
                    throw new LogicException('invalid param sort');
                }
                
                if (!in_array(strtoupper($orderWay), ['ASC', 'DESC'])) {
                    throw new LogicException('invalid param order');
                }
            }
        } else {
            if ($this->sort && preg_match('/(^_([a-zA-Z0-9\.]_?)*$)|(^[a-zA-Z](_?[a-zA-Z0-9\.])*_?$)/', $this->sort) == false) {
                throw new LogicException('invalid param sort');
            }
            
            if (!in_array(strtoupper($this->order), ['ASC', 'DESC'])) {
                throw new LogicException('invalid param order');
            }
        }
        
        $wheres = [];
        
        foreach ($filters as $key => $param) {
            
            if (preg_match('/(^_([a-zA-Z0-9\.]_?)*$)|(^[a-zA-Z](_?[a-zA-Z0-9\.])*_?$)/', $key) == false) {
                throw new LogicException(sprintf('invalid param filter[%s]', $key));
            }
            
            $op         = isset($ops[$key]) ? $ops[$key] : '=';
            $prepareKey = str_replace('.', '_', $key);
            
            switch (strtoupper($op)) {
                case '=':
                case '<>':
                case '!=':
                case '>':
                case '>=':
                case '<':
                case '<=':
                    $wheres[sprintf('%s %s :%s', $key, $op, $prepareKey)] = [
                        $prepareKey => $param,
                    ];
                    break;
                case 'LIKE':
                case 'NOT LIKE':
                    $wheres[sprintf('%s %s :%s', $key, $op, $prepareKey)] = [
                        $prepareKey => $param,
                    ];
                    break;
                case 'RANGE':
                case 'NOT RANGE':
                    $op = str_replace('RANGE', 'BETWEEN', $op);
                
                case 'BETWEEN':
                case 'NOT BETWEEN':
                    
                    if (stripos($param, ',') === false) {
                        $params = array_slice(explode(' - ', trim($param, ' - ') . ' - '), 0, 2);
                    } else {
                        $params = array_slice(explode(',', trim($param, ',') . ','), 0, 2);
                    }
                    
                    //当出现一边为空时改变操作符
                    if ($params[0] === '') {
                        $op    = $op == 'BETWEEN' ? '<=' : '>';
                        $param = $params[1];
                        
                        $wheres[sprintf('%s %s :%s', $key, $op, $prepareKey)] = [
                            $prepareKey => $param,
                        ];
                    } elseif ($params[1] === '') {
                        $op    = $op == 'BETWEEN' ? '>=' : '<';
                        $param = $params[0];
                        
                        $wheres[sprintf('%s %s :%s', $key, $op, $prepareKey)] = [
                            $prepareKey => $param,
                        ];
                    } else {
                        $wheres[sprintf('%s %s :%s and :%s', $key, $op, $prepareKey . '0', $prepareKey . '1')] = [
                            $prepareKey . '0' => $params[0],
                            $prepareKey . '1' => $params[1],
                        ];
                    }
                    break;
                case 'IN':
                case 'NOT IN':
                    $wheres[sprintf('%s %s (:%s)', $key, $op, $prepareKey)] = [
                        $prepareKey => explode(',', $param),
                    ];
                    break;
                default:
                    throw new LogicException(sprintf('invalid param op[%s]', $op));
            }
        }
        
        $aWhere    = [];
        $bindValue = [];
        
        foreach ($wheres as $where => $fun) {
            if (is_callable($fun)) {
                $value = $fun();
                if ($value !== false) {
                    $aWhere[]  = $where;
                    $bindValue = array_merge($bindValue, $value);
                }
            } else {
                if ($fun !== false) {
                    $aWhere[]  = $where;
                    $bindValue = array_merge($bindValue, $fun);
                }
            }
            
        }
        
        $where = $aWhere ? ' AND ' . implode(' AND ', $aWhere) : '';
        
        $limit = '';
        $order = '';
        
        if ($this->limit < 0)
            throw new LogicException('invalid param limit');
        
        if ($this->offset < 0)
            throw new LogicException('invalid param offset');
        
        if ($this->limit) {
            $limit = sprintf(' limit %d,%d ', $this->offset, $this->limit);
        }
        
        if ($this->sort) {
            if (is_array($this->sort)) {
                $order = ' order by ';
                foreach ($this->sort as $sort) {
                    $aSort     = explode('-', $sort, 2);
                    $sortField = $aSort[0];
                    $orderWay  = $this->order;
                    if (count($aSort) > 1) {
                        $orderWay = $aSort[1];
                    }
                    $order .= sprintf('%s %s,', $sortField, $orderWay);
                }
                $order = rtrim($order, ',');
            } else {
                $order = sprintf(' order by %s %s ', $this->sort, $this->order);
            }
        }
        
        return [
            'where'     => $where,
            'bindValue' => $bindValue,
            'limit'     => $limit,
            'order'     => $order,
        ];
    }
}